/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package neuronalenetzeelearning.model;

import java.util.Random;
import java.util.Vector;

/**
 *
 * @author Maria
 */
public abstract class LearningRule implements ILearningRule {

    public static final double DEFAULT_LEARNINGRATE = 0;
    //public static final double DEFAULT_CHANGEWEIGHT = 0;
    Vector<Vector<Double>> DEFAULT_INPUTVECTOR = new Vector<Vector<Double>>();
    public double learningRate;
    public Vector<Matrix> latestNMatrices;
    private Graph graph;
    private int maxNumberOfLearningSteps = 1000;
    public StepFunction stepFunction;
    public Vector<Vector<Double>> possibleInputVectors; //vorgegebene Eingabe-Vektoren


    private LearningRule() {
        this(new Graph(), DEFAULT_LEARNINGRATE, StepFunction.HEAVISIDELINEAR);
    }

    /**
     * Konstruktor
     * @param graph hieraus wird die Anzahl der Knoten und Kanten und die Kantengewichte ausgelesen
     * @param learningRate global festgelegte Lernrate
     */
    public LearningRule(Graph graph, double learningRate, StepFunction stepFunction) {
        this.graph = graph;
        this.learningRate = learningRate;
        this.stepFunction= stepFunction;
        this.latestNMatrices = new Vector<Matrix>();
    }

    public LearningRule(Graph graph, double learningRate, StepFunction stepFunction, Vector<Vector<Double>> IV) {
        this.graph = graph;
        this.learningRate = learningRate;
        this.stepFunction= stepFunction;
        this.possibleInputVectors=IV;
        this.latestNMatrices = new Vector<Matrix>();
    }

    public void setStepFunction(StepFunction stepFunction) {
        this.stepFunction = stepFunction;
    }

    public StepFunction getStepFunction() {
        return this.stepFunction;
    }

   /**
     * wird in den Klassen
     * HebbLearningRule und SupervisedLearning ueberschrieben
     */
    @Override
    public abstract void getChangeWeight(Graph graph);

    @Override
    public abstract void nextStep();

    @Override
    public double getLearningRate() {
        return this.learningRate;
    }

    @Override
    public void setLearningRate(double learningRate) {
        this.learningRate = learningRate;
    }

    public Graph getGraph() {
        return this.graph;
    }

    public void setRandomInput(){
        Random rand = new Random();
        int number = rand.nextInt(this.possibleInputVectors.size());
        for(int i=0; i<graph.getLayer1Nodes().size(); i++){
            graph.getLayer1Nodes().get(i).setInputValue(this.possibleInputVectors.elementAt(number).elementAt(i));
        }
    }

    /* 
     * Diese Methode überprüft, ob das NN "gelernt" hat, dh. ob die Gewichtsmatrix
     * im Laufe der Zeit stabil geworden ist.
     */
    public boolean hasLearned() {
        boolean haslearned = false;

        int N;
        // Die verschiedenen Eongabevekoren(EV) wirken zufällig auf das NN ein;
        // Hier ist N bezüglich der Anzahl der EV(hier max.5) so gewählt,
        // dass zu 95% zu erwarten ist, dass jeder der EV mind 1x das NN beeinflusst hat
        switch(this.possibleInputVectors.size()){
            case 1: N=1;
            case 2: N=6;
            case 3: N=10;
            case 4: N=15;
            case 5: N=21;
            default : N=30;
        }
        
        if(!this.latestNMatrices.isEmpty() && this.latestNMatrices.size()>N){
            this.latestNMatrices.removeElementAt(0);
        }
        this.latestNMatrices.addElement(this.graph.getMatrix()); 
        
        if(latestNMatrices.size()<N){
            this.nextStep();
            return false;
        }

        // ist die Gewichtsmatrix N Lernschritte lang stabil, so ist anzunehmen, dass das NN gelernt hat
        for(int n=0; n<N; n++){
            this.nextStep();

            //vergleiche die Matrizen(vor dem Lernen) mit der Matrix nach 1 Lernschritt
            if((int)(this.latestNMatrices.get(n).getValues()[0][0]*1000) == (int)(graph.getMatrix().getValues()[0][0]*1000)){
                haslearned=true;
                for(int i=0; i<this.latestNMatrices.get(n).getValues().length&&haslearned; i++){
                    for(int j=0; j<this.latestNMatrices.get(n).getValues()[0].length&&haslearned; j++){
                        if((int)(this.latestNMatrices.get(n).getValues()[i][j]*1000)!=(int)(graph.getMatrix().getValues()[i][j]*1000)){
                            haslearned=false;
                        }
                    }
                }
            }
        }


        return haslearned;
    }

    /*
     * Analoge Methoden auch in der Klasse Propertycontroller vorhanden;
     */
    public int RestrictedLearningIteration(int n){
        for (int i=1; i<=n; i++){

            if(this.hasLearned()){
                System.out.println("      Gewichtsmatrix stabil");
                return i;
            }
        }
        return n;
    }

    public int UnRestrictedLearningIteration(){
        return RestrictedLearningIteration(this.maxNumberOfLearningSteps);
    }
}
